// 纯函数  优点:可缓存，提高性能
// function memoize(fn){
//     const obj = {};
//     return function(){
//         let key = JSON.stringify(arguments)   //arguments是伪数组，可转成字符串来使用
//         obj[key] = obj[key]||fn.apply(this,arguments)//如果有就赋值或者调用fn 传参传arguments 因为arguments是伪数组，所以需要用apply的第二个参数来展开伪数组或数组来传参
//         return obj[key]
//     }
// }
// function yuan(r){
//     console.log(r*r)

//     return Math.PI*r*r;
// }
// let call = memoize(yuan)
// console.log(call(4))
// console.log(call(4))
// console.log(call(4))

// 纯函数  因为一直有输入和输出，测试的目的只要找到函数的结果，所以方便测试
// 多线程运行下 纯函数因为不需要依赖共享的内存数据，只依靠参数，所以多线程对共享的内存数据的改变并不会影响纯函数的运行
// es6 之后推出了一个 web Worker（可以开启新的线程）
/**************************************************************************************** */
//js执行中会把函数提前执行
// 函数的柯里化
// 当一个函数有多个参数的时候，可以先传一部分参数来调用这个函数（这部分参数一直不变），当调用这个函数的时候会返回
// 一个新的函数，这个函数接受剩余的参数并返回结果
// 可以把任意多元函数转化为一元函数
// 例：模拟lodash中的curry(柯里化)
// function getsum(a,b,c,d,e){
//     return a+b+c+d+e;
// }
// const curr = curry(getsum)
// function curry(func){
//     return function curryedfn(...args){
//         if(args.length<func.length){//如果实际的参数长度小于应该有的参数的长度
//             return function(){//继续执行上面的方法  因为这里的调用形成了闭包，会把第一次调用传的参数保留在agrs里面
//                 console.log(args,arguments.length)
//                 return curryedfn(...args.concat(Array.from(arguments)))//第一次调用的参数和剩余参数拼接,arguments是伪数组，需要转换成数组进行拼接，最后得到数组传参需要展开所以加...
//             }
//         }
//         return func(...args)
//     }
// }
// console.log(curr(1)(2)(3)(4)(5))
//模拟了lodash中的curry函数，最终结果就是要把所有参数拼接起来最后输出
//柯里化运用闭包，将之前传的参数缓存起来不被释放，然后可以拿出来和剩余参数拼接来达到我们的输出最后结果的目的
//让函数变的灵活，函数粒度更小
/******************************************************************************************************* */
// 函数的组合
// 模拟lodash中的函数的组合 flowRight
//例：
// const reverse = arr=>arr.reverse();
// const first = arr =>arr[0];
// const toupper = str =>str.toUpperCase()
// const fn = compose(toupper,first,reverse)
// function compose(...args){//因为不知道传参的个数，所以用es6语法中剩余参数的方法
//     return function(val){//我们这个函数返回值是一个函数所以也需要接收一个参数
//          因为flowRight是从右向左执行所以先把args这个参数翻转一下，args代表多个函数，
//          reduce 依次执行数组中的方法 第一个参数是个函数，函数的第一个参数是上次一执行的值的汇总，第二个参数的数组中的每个方法；第二个参数是初始汇总的值
//         return args.reverse().reduce((Cumulative,fn)=>{
//             return fn(Cumulative)
//         },val)
//     }
// }
// 箭头函数改造
// const compose = (...args)=>(val)=>args.reverse().reduce((Cumulative,fn)=>fn(Cumulative),val)
// console.log(fn(['one','two','three']))
// 函数的结合律
// 先结合前俩个和先结合后俩个结果是一样的
// const fn = _.flowRight(_.toUpper,_.first,_.reverse)
// const fn = _.flowRight(_.flowRight(_.toUpper,_.first),_.reverse)
// const fn = _.flowRight(_.toUpper,_.flowRight(_.first,_.reverse))
// console.log(fn(['one','two','three']))
//组合的函数是一个元函数，如果不是要柯里化函数
// lodash和lodash/fp
// lodash/fp更适用于我们书写函数式编程 fp模块中提供的方法都是已经柯里化的方法 多个参数的话都是函数优先数据置后(因为我们要的就是数据)这些方法在函数组合的时候都可以直接使用
// 例：const fp = require('lodash/fp');
// const fn = fp.flowRight(fp.join('-'),fp.map(fp.toLower),fp.split(' '))
//*********** */
// pointfree 具体实现是函数的组合 在书写的过程中不用考虑要传入的值
 /***************************************************************************************/
//  函子  functor
// 函数式编程的运算不直接操作值 ，而是由函子完成
// 函子是实现了map契约的对象  所有的函子都有一个map对象
// 函子相当于一个盒子，这个盒子里封装了一个值
// 想要处理盒子里的值  需要给盒子里的map方法传递一个处理值的函数（这个函数为纯函数），由这个函数来处理值
// 当map方法处理完之后会返回一个包含新值的盒子（也叫函子）
//各种函子
// MayBe 函子  对外部空值做处理（null， undefined），把副作用控制在可控范围
// 缺点 多次调用map时,并不知道是哪个函子返回的null
// either函子 处理异常，记录错误信息并返回
//IO 函子  I  input(输入) O output（输出） 
// IO函子内部包装了一些函数  这些函数可能不纯，但是IO函子始终是纯的  
// IO函子延迟了不纯的发生，将副作用控制在可控的范围内发生
//folktale  标准的函数式编程库
// Task函子执行异步任务
//Pointed函子 指实现了of静态方法的函子  of方法是避免我们使用new来创建对象，更深层的是of方法用来把值放到上下文Context(把值放到容器中用map来处理值)
// Monad 函子  处理函子嵌套函子的问题 当我们想合并一个函数 返回值的时候用map处理函子的值，返回函子的时候flatMap处理函子中的函子
